// Copyright (C) 2014 Sony Computer Entertainment Inc.
// All Rights Reserved.

#ifndef PooledPageAllocationAligned_h
#define PooledPageAllocationAligned_h

#if OS(ORBIS) && ENABLE(DETACHED_JIT)

#include "JITBridge.h"
#include "PageBlock.h"

namespace manx {

class PooledPageAllocationAligned {
public:
    PooledPageAllocationAligned(
        size_t chunkAlignment,
        size_t chunkSize,
        size_t maxPoolSize,
        char const * name = "")
        : m_chunkAlignment(chunkAlignment)
        , m_chunkSize(chunkSize)
        , m_freelist(0)
        , m_poolSize(0)
        , m_maxPoolSize(maxPoolSize)
    {
        ASSERT(isPowerOfTwo(chunkSize));
        ASSERT(isPowerOfTwo(chunkAlignment));

        snprintf(m_name, sizeof(m_name), "%s", name);
    }

    void * allocate()
    {
        static size_t poolMiss(0);
        static size_t poolHit(0);
        static bool   verbose(getenv("JSCORE_POOLEDGCHEAP_VERBOSE") ? true : false);

        if (!m_freelist)
            refill();

        void * page(0);
        if (m_freelist) {
            // allocate new page
            page = reinterpret_cast<void*>(m_freelist);

            // remove chunk from the list
            m_freelist = *(reinterpret_cast<uintptr_t*>(m_freelist));
            ++poolHit;
        } else
            ++poolMiss;

        if (verbose && ((poolHit && !(poolHit%100)) || (poolMiss && !(poolMiss%100))))
            fprintf(stderr, "[PooledPage stats] (%s) hits=%zd  miss=%zd\n", m_name, poolHit, poolMiss);

        return page;
    }

    void deallocate(void *p)
    {
        *(reinterpret_cast<uintptr_t*>(p)) = m_freelist;
        m_freelist = reinterpret_cast<uintptr_t>(p);

        // decommit pages
        size_t const pagesize(getpagesize());
        if (!(m_chunkAlignment % pagesize)) {
            size_t pages(m_chunkSize / pagesize);
            uintptr_t mem(reinterpret_cast<uintptr_t>(p));

            // preserve data in the first page (contains linked list information)
            if (pages) {
                madvise(reinterpret_cast<void*>(mem), pagesize, MADV_DONTNEED);
                --pages;
            }

            // release data in successive pages if any
            if (pages)
                madvise(reinterpret_cast<void*>(mem + pagesize), pages * pagesize, MADV_FREE);
        }
    }

    void refill()
    {
        ASSERT(!m_freelist);

        // check limit
        size_t const refillSize(m_chunkSize * s_chunksPerRefill);
        if (m_poolSize + refillSize > m_maxPoolSize)
            return;

        // allocate memory for the pool
        m_freelist = reinterpret_cast<uintptr_t>(JITSharedDataMemory::shared_memalign(m_chunkAlignment, refillSize));
        if (!m_freelist)
            return;
        m_poolSize += refillSize;

        // initialize the singly linked-list
        uintptr_t chunk(m_freelist);
        for (size_t i = 0; i < s_chunksPerRefill - 1; ++i) {
            uintptr_t nextChunk = chunk + m_chunkSize;
            *(reinterpret_cast<uintptr_t*>(chunk)) = nextChunk;
            chunk = nextChunk;
        }
        *(reinterpret_cast<uintptr_t*>(chunk)) = 0;
    }

public:
    size_t m_chunkAlignment;
    size_t m_chunkSize;

private:
    static size_t const s_chunksPerRefill = 32;
    uintptr_t m_freelist;
    size_t m_poolSize;
    size_t m_maxPoolSize;
    char m_name[32];
};

} // namespace manx

#endif

#endif // PooledPageAllocationAligned_h
